package midas.mw.crt.MD;

import java.util.Hashtable;

import midas.mw.cfg.CfgLogging;
import midas.mw.cfg.log.Logger;
import midas.mw.crt.CrtImpl;
import midas.mw.crt.QueueThread;
import midas.mw.crt.common.IPMessage;

public class PriorityTable {
	
	/**
	 * The thread in charge of sending stored messages associated to this PriorityTable
	 */
	private QueueThread queueThread;
	
	/**
	 * Table to store the MsgQueues. 
	 * There is one queue of messages for each priority.
	 */
	private Hashtable queueTable;
	
	/**
	 * Value to set the number of possible priorities of a message.
	 * One message has three possible priorities:
	 *    0 - LOW priority. 
	 *    1 - MEDIUM priority.
	 *    2 - HIGH priority.
	 *    
	 */ 
	final byte PRIORITIES = 3; 
		
	/**
	 * Status of this PriorityTable. It shows if some process
	 * is using it, i.e. some application sending a message
	 */
	public boolean status = false;
	 	
	/**
	 * Constructor - Create an instance of a PriorityTable.
	 * This constructor creates a new instance of this class and initializes the
	 * queueTable.
	 * The queueTable is initialized with three MsgQueues, one for each priority
	 * value (0 LOW, 1 MEDIUM, 2 HIGH).
	 * 
	 */
	public PriorityTable()
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "PriorityTable()");
		
		this.queueTable = new Hashtable();
		for (int i=0; i< PRIORITIES; i++)
			queueTable.put(Integer.toString(i),new MsgQueue());	
	}

	/**
	 * Inserts a IPMessage into its corresponding message queue in the priority
	 * table
	 * This function inserts the IPMessage at the end (bottom) of its 
	 * corresponding MsgQueue. 
	 * 
	 * @param ipMessage IPMessage to be inserted in the "Priority Table" structure.
	 */
	public void insertMessage(IPMessage ipMessage)
	{
		//En este caso no hace falta sincronizar porque el unico sitio desde el que se la llama es desde
		//MessageDispatcher.enqueueMessage -> NodeListMD.insertIPMessage
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "PriorityTable().insertMessage(IPMessage)");
		
		((MsgQueue) queueTable.get(Integer.toString(ipMessage.getPriority()))).insert(ipMessage);
	}
	
	/**
	 * Returns whether a message queue is empty or not.
	 * This function retrieves true when the message queue specified by the 
	 * parameter 'priority' is empty, and false if the queue has got any message.
	 * It delegates the operation to the message queue data type (MsgQueue), 
	 * just calling MsgQueue.isEmpty() function.
	 * 
	 * @param priority Priority of the MsgQueue to see if it's empty.
	 * @return true if the message queue is empty; false otherwise
	 */
	private boolean isQPriorityEmpty(byte priority)
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "PriorityTable().isQPriorityEmpty(" + priority + ")");
		
		return ( (MsgQueue) this.queueTable.get(Byte.toString(priority)) ).isEmpty();
	}
	
	/**
	 * Returns true if PriorityTable has no messages.
	 * This function retrieves false when any message queue in the PriorityTable 
	 * is not empty, and false if there is no messages in this structure.
	 * It looks for messages in each message queue of the Priority Table, and 
	 * when it finds any one that it is not empty it returns false. 
	 * It delegates the operation to the message queue data type (MsgQueue), 
	 * just calling MsgQueue.isEmpty() function.
	 * 
	 * @return true if Priority Table is empty; false otherwise.
	 */
	public boolean isEmptyTable()
	{
		//En este caso no hace falta sincronizar porque el unico sitio desde el que se la llama es desde
		//NodeListMD.printStruct y esta sincronizado
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "PriorityTable().isEmptyTable()");
		
		for (int i=0; i<PRIORITIES; i++)
		{
			if (!((MsgQueue) queueTable.get(Integer.toString(i))).isEmpty())
				return false;
		}
		return true;
	}

	/**
	 * Returns the first message that is waiting in the message queue with higher
	 * priority (the first message according to the sending policy).
	 * This function returns the first message that has to be sent. 	 
	 * It looks for the message watching sequentially in each message queue. 
	 * 
	 * @return The first message to be sent that is stored in the priority table.
	 */
	public IPMessage seeFirstToBeSent()
	{
		//En este caso no hace falta sincronizar porque el unico sitio desde el que se la llama es desde
		//NodeListMD.seeMessageNode y esta sincronizado
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "PriorityTable().seeFirstTobeSent()");
		
		for(int i=PRIORITIES-1; i>=0; i--)
		{
			if(!(((MsgQueue) queueTable.get(Integer.toString(i))).isEmpty()))
				return ((MsgQueue)queueTable.get(Integer.toString(i))).seeTop();				
		}
		return null;
		
	}
	
	/**
	 * Prints out the Priority Table's structure.
	 * This function shows on screen all the structure of the "Priority Table".
	 * It takes out each string representation of a message queue and prints it out.
	 */
	public void printTable()
	{
		//En este caso no hace falta sincronizar porque el unico sitio desde el que se la llama es desde
		//NodeListMD.printStruct y esta sincronizado
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "PriorityTable().printTable()");
		
		for (int i=0; i<PRIORITIES; i++)
		{			
			CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "PriorityTable().printTable(). Priority queue: " + i);
			if (((MsgQueue) this.queueTable.get(Integer.toString(i))).isEmpty())
				CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "PriorityTable().printTable(). Empty queue");				
			else
				CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "PriorityTable().printTable().\r\n" + ((MsgQueue) queueTable.get(Integer.toString(i))).passToString());
		}
	}
	
	/**
	 * Deletes a message from the Priority Table.
	 * This method erases the IPMessage specified by the parameter.
	 * It looks for its corresponding priority queue and delegates the delete operation
	 * of the message to the MsgQueue Data Type.
	 * The message is erased from the Priority Table just calling MsgQueu.deleMessage() method.
	 * 
	 * @param ipMessage Message to be deleted.
	 */
	public void deleteMessage(IPMessage ipMessage)
	{
		//En este caso no hace falta sincronizar porque el unico sitio desde el que se la llama es desde
		//NodeListMD.deletemessage y esta sincronizado
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "PriorityTable().deleteMessage(IPMessage)");
		
		if (!isQPriorityEmpty(ipMessage.getPriority())){
			((MsgQueue)queueTable.get(Byte.toString(ipMessage.getPriority()))).deleteMessage(ipMessage);
		}
	}	
	
	/**
	 * Sets the status of the PriorityTable
	 * @param status Status to be set. True if some process is using it, false 
	 * 			otherwise
	 */
	public void setStatus(boolean status) 
	{
		//En este caso no hace falta sincronizar porque el unico sitio desde el que se la llama es desde
		//NodeListMD.setStatus y esta sincronizado
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "PriorityTable().setStatus(" + status + ")");
		
		this.status = status;
	}
	
	/**
	 * Gets the status of the PriorityTable
	 * @return True if some process is using it, false otherwise
	 */
	public boolean getStatus() 
	{
		//En este caso no hace falta sincronizar porque el unico sitio desde el que se la llama es desde
		//NodeListMD.getStatus y esta sincronizado
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "PriorityTable().getStatus()");
		
		return status;
	}
	
	/**
	 * Gets the thread in charge of sending stored messages associated to this PriorityTable
	 *
	 * @return The thread in charge of sending stored messages associated to this PriorityTable
	 */
	public QueueThread getQueueThread()
	{
		return queueThread;
	}
	
	/**
	 * Sets the thread in charge of sending stored messages associated to this PriorityTable
	 *
	 * @param crtImpl Instance of the CRT running in the node
	 * @param nodeId Midas node identifier of the node associated to this PriorityTable
	 * @return The thread in charge of sending stored messages associated to this PriorityTable
	 */
	public QueueThread setQueueThread(CrtImpl crtImpl, String nodeId)
	{
		queueThread = new QueueThread(crtImpl, nodeId);		
		return queueThread;
	}
}
